Attaching package: 㤼㸱lubridate㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    date
Parsed with column specification:
cols(
  .default = col_double(),
  prosper_rating = col_character(),
  origination_date = col_datetime(format = ""),
  loan_status_description = col_character(),
  loan_default_reason = col_logical(),
  loan_default_reason_description = col_logical(),
  next_payment_due_date = col_datetime(format = ""),
  co_borrower_application = col_logical()
)
See spec(...) for full column specifications.
266 parsing failures.
 row                             col           expected     actual              file
1957 loan_default_reason             1/0/T/F/TRUE/FALSE 2          'prosper2019.csv'
1957 loan_default_reason_description 1/0/T/F/TRUE/FALSE Bankruptcy 'prosper2019.csv'
3041 loan_default_reason             1/0/T/F/TRUE/FALSE 3          'prosper2019.csv'
3041 loan_default_reason_description 1/0/T/F/TRUE/FALSE Deceased   'prosper2019.csv'
3595 loan_default_reason             1/0/T/F/TRUE/FALSE 3          'prosper2019.csv'
.... ............................... .................. .......... .................
See problems(...) for more details.

pros_agg = pros_df%>%
  mutate(yr = year(origination_date),mn = month(origination_date))%>%
  #group_by(yr,mn, prosper_rating)%>%
  group_by(origination_date,prosper_rating,yr,mn)%>%
  summarize(avg_amt_borrowed = mean(amount_borrowed),
            w_avg_rate = weighted.mean(borrower_rate,amount_borrowed))%>%
  mutate(org_dt = ymd(paste(yr,mn,'01',sep = '-')))
  
pros_agg2 = pros_df%>%
  mutate(yr = year(origination_date),mn = month(origination_date))%>%
  group_by(yr,mn, prosper_rating)%>%
  #group_by(origination_date,prosper_rating,yr,mn)%>%
  summarize(avg_amt_borrowed = mean(amount_borrowed),
            w_avg_rate = weighted.mean(borrower_rate,amount_borrowed))%>%
  mutate(org_dt = ymd(paste(yr,mn,'01',sep = '-')))

A little about me before we begin:

Modeler at Bank of America

Avid Star Wars Fan

Amatuer user and ardent supporter of version control in data science

R user for the last 9 years.

Used R for modeling and development at American Credit Acceptance before coming to Bank of America.

Bank of America work:

What is this presenation?

I enjoy the data visualization side of data science

ggplot was always go too for my data science needs

  • simple intuitive syntax to write and read
  • wide range of useful geoms to solve common modeling problems
  • integrates well with the rest of tidyverse

  • (Big step forward from lattice and base graphics)

ggplot is an adoption of the precepts laid out in the Grammar of Graphics

ggplot doesn’t lend itself to interactivity

  • several efforts have introduced interactivity
    • ggvis
    • rggobi
    • iPlots
    • htmlwidgets
    • r2d3

htmlwidets and r2d3 are an adaptation of D3/javascript technology

D3 is a javascript package that is very important in data visualization

  • Written in a language that integrates with web development
  • Uses data to manipulate the web document through various objects (SVG)
  • Released in 2011

Useful examples of power and interactivity:

Sunburst Example

Density over time

Decision Tree Example

D3 Pros and Cons

Pros - Very flexible and portable - Interactivity part of the dna - Looks very professional and polished

Cons - Very steep learning curve - API requires decent understanding of how javascript - Centered on webdevelopment instead of data science - ?Falling out of favor?

What is the answer?

Plot.ly is a solution with a simpler API and out of the box interactivity

Essentially plot.ly is API wrapper for several D3

How does the plotly package work

Key to understading package is understanding how it transforms the data

Below is a useful diagram showing how the final presentation is done.

Plotly uses two key components:

  1. Data/Trace:
  1. Layout

ggplotly() to the rescue

If you have plots in ggplot, you can start using plotly with just a simple function call on most ggplot objects.

Let’s make a ggplot from Propser data.

pros_scat = pros_df_sam%>%
  ggplot(aes(principal_paid, interest_paid))+geom_point(aes(color = factor(prosper_rating)))

pros_scat

ggplotly(pros_scat)

NA


pros_df_sam%>%
  plot_ly(x = ~principal_paid, y = ~interest_paid)%>%
  add_markers(color = ~prosper_rating)

Do it again, but with lines:

amt_fin_p = pros_agg%>%
  ggplot(aes(origination_date,avg_amt_borrowed))+
  geom_line(aes(color = prosper_rating))

amt_fin_p

Simple ggplotly command adds the tooltip, zooming,

ggplotly(amt_fin_p)

Now the plotly syntax:

Same Graph different syntax (using the add_* trace addition)

pros_agg%>%
  ungroup()%>%
  plot_ly(x = ~origination_date, y = ~avg_amt_borrowed)%>%
  add_lines(color = ~prosper_rating)

Histogram:

Histogram with muiltple factors

Now lets do this with 2 dimentions


subplt = subplot(
  
  pros_df_sam%>%plot_ly(x = ~principal_paid, color = I("black"),type = 'histogram'),
  plotly_empty(),
  pros_df_sam%>%plot_ly(x = ~principal_paid,y = ~interest_paid,type = 'histogram2dcontour'),
  pros_df_sam%>%plot_ly(y = ~interest_paid,color = I("black"),type = 'histogram'),
  nrows = 2,  
  heights = c(0.2,0.8), 
  widths = c(0.8,0.2),
  shareX = TRUE,
  shareY = TRUE
)
No trace type specified and no positional attributes specifiedNo trace type specified:
  Based on info supplied, a 'scatter' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter
No scatter mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
p = layout(subplt, showlegend = FALSE)
  
p

NA
x <- rnorm(1000)
y <- rnorm(1000)
s <- subplot(
  plot_ly(x = x, color = I("black"), type = 'histogram'), 
  plotly_empty(), 
  plot_ly(x = x, y = y, type = 'histogram2dcontour', showscale = F), 
  plot_ly(y = y, color = I("black"), type = 'histogram'),
  nrows = 2, heights = c(0.2, 0.8), widths = c(0.8, 0.2), 
  shareX = TRUE, shareY = TRUE, titleX = FALSE, titleY = FALSE
)
No trace type specified and no positional attributes specifiedNo trace type specified:
  Based on info supplied, a 'scatter' trace seems appropriate.
  Read more about this trace type -> https://plot.ly/r/reference/#scatter
No scatter mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
p <- layout(s, showlegend = FALSE)

p

What does that look like in ggplot? Doable but not intuitive.

library(gridExtra)

hist_top <- pros_df_sam%>%ggplot(aes(principal_paid))+geom_histogram()
empty <- ggplot()+geom_point(aes(1,1), colour="white")+
         theme(axis.ticks=element_blank(), 
               panel.background=element_blank(), 
               axis.text.x=element_blank(), axis.text.y=element_blank(),           
               axis.title.x=element_blank(), axis.title.y=element_blank())

scatter <- ggplot()+geom_density_2d(aes(pros_df_sam$principal_balance, pros_df_sam$interest_paid))

hist_right <- pros_df_sam%>%ggplot(aes(interest_paid))+geom_histogram()+coord_flip()

grid.arrange(hist_top, empty, scatter, hist_right, ncol=2, nrow=2, widths=c(4, 1), heights=c(1, 4))

Ack. Not as good.

Now some plots that are very difficult to do in ggplot and rely on the interactivity heavily.

Sunburst

First step to making this one work is processing the data:

Must have labels, parents and values.

Will use the Titanic dataset because it has some nice categories and values to take on:

agg1 = titanic_df%>%
  group_by(Survived)%>%
  summarize(value = sum(Freq))%>%
  mutate(id = as.character(Survived),
         label = as.character(Survived),
         parent = "")%>%
  select(label, id, parent, value)

agg2 = titanic_df%>%
  group_by(Survived,Class)%>%
  summarize(value = sum(Freq))%>%
  ungroup()%>%
  mutate(id = paste(Survived, Class, sep = ' - '),
         parent = as.character(Survived),
         label = as.character(Class))%>%
  select(label, id, parent, value)

agg3 = titanic_df%>%
  group_by(Survived, Class, Age)%>%
  summarize(value = sum(Freq))%>%
  ungroup()%>%
  mutate(id = paste(Survived, Class, Age, sep = ' - '),
         parent = paste(Survived, Class, sep = ' - '),
         label = as.character(Age))%>%
  select(label, id, parent, value)


agg4 = titanic_df%>%
  group_by(Survived,Class,Age,Sex)%>%
  summarize(value = sum(Freq))%>%
  ungroup()%>%
  mutate(id = paste(Survived, Class, Age, Sex, sep = ' - '),
         parent = paste(Survived, Class, Age, sep = ' - '),
         label = as.character(Sex))%>%
  select(label, id, parent, value)


agg =bind_rows(agg1,agg2)%>%bind_rows(agg3)%>%bind_rows(agg4)%>%filter(value > 0)



head(agg)

library(plotly)

d <- data.frame(
    ids = c(
    "North America", "Europe", "Australia", "North America - Football", "Soccer",
    "North America - Rugby", "Europe - Football", "Rugby",
    "Europe - American Football","Australia - Football", "Association",
    "Australian Rules", "Autstralia - American Football", "Australia - Rugby",
    "Rugby League", "Rugby Union"
  ),
  labels = c(
    "North<br>America", "Europe", "Australia", "Football", "Soccer", "Rugby",
    "Football", "Rugby", "American<br>Football", "Football", "Association",
    "Australian<br>Rules", "American<br>Football", "Rugby", "Rugby<br>League",
    "Rugby<br>Union"
  ),
  parents = c(
    "", "", "", "North America", "North America", "North America", "Europe",
    "Europe", "Europe","Australia", "Australia - Football", "Australia - Football",
    "Australia - Football", "Australia - Football", "Australia - Rugby",
    "Australia - Rugby"
  ),
  stringsAsFactors = FALSE
)

plot_ly(d, ids = ~ids, labels = ~labels, parents = ~parents, type = 'sunburst')%>%
library(rjson)

json_file <- "https://raw.githubusercontent.com/plotly/plotly.js/master/test/image/mocks/sankey_energy.json"
json_data <- fromJSON(paste(readLines(json_file), collapse=""))

p <- plot_ly(
    type = "sankey",
    domain = list(
      x =  c(0,1),
      y =  c(0,1)
    ),
    orientation = "h",
    valueformat = ".0f",
    valuesuffix = "TWh",

    node = list(
      label = json_data$data[[1]]$node$label,
      color = json_data$data[[1]]$node$color,
      pad = 15,
      thickness = 15,
      line = list(
        color = "black",
        width = 0.5
      )
    ),

    link = list(
      source = json_data$data[[1]]$link$source,
      target = json_data$data[[1]]$link$target,
      value =  json_data$data[[1]]$link$value,
      label =  json_data$data[[1]]$link$label
    )
  ) %>% 
  layout(
    title = "Energy forecast for 2050<br>Source: Department of Energy & Climate Change, Tom Counsell via <a href='https://bost.ocks.org/mike/sankey/'>Mike Bostock</a>",
    font = list(
      size = 10
    ),
    xaxis = list(showgrid = F, zeroline = F),
    yaxis = list(showgrid = F, zeroline = F)
)

p
LS0tDQp0aXRsZTogIlBsb3QubHk6IEhvdyBJIGxlYXJuZWQgYSAodmVyeSBsaXR0bGUpIGphdmEgc2NyaXB0IGFuZCBsb3ZlIGludGVyYWN0aXZlIHBsb3RzOiINCmF1dGhvcjogIkRhdmlkIEdyaW5kZXIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciwgZWNobz0gRkFMU0V9DQojSW1wb3J0IGxpYnJhcmllcw0KDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkobGlzdHZpZXdlcikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpgYGANCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCiNJbXBvcnRpbmcgZGF0YQ0KcHJvc19kZiA9IHJlYWRfY3N2KCJwcm9zcGVyMjAxOS5jc3YiKQ0KcHJvc19kZl9zYW0gPSBwcm9zX2RmW3NhbXBsZShucm93KHByb3NfZGYpLDEwMDAwKSxdDQpgYGANCg0KYGBge3J9DQoNCnByb3NfYWdnID0gcHJvc19kZiU+JQ0KICBtdXRhdGUoeXIgPSB5ZWFyKG9yaWdpbmF0aW9uX2RhdGUpLG1uID0gbW9udGgob3JpZ2luYXRpb25fZGF0ZSkpJT4lDQogICNncm91cF9ieSh5cixtbiwgcHJvc3Blcl9yYXRpbmcpJT4lDQogIGdyb3VwX2J5KG9yaWdpbmF0aW9uX2RhdGUscHJvc3Blcl9yYXRpbmcseXIsbW4pJT4lDQogIHN1bW1hcml6ZShhdmdfYW10X2JvcnJvd2VkID0gbWVhbihhbW91bnRfYm9ycm93ZWQpLA0KICAgICAgICAgICAgd19hdmdfcmF0ZSA9IHdlaWdodGVkLm1lYW4oYm9ycm93ZXJfcmF0ZSxhbW91bnRfYm9ycm93ZWQpKSU+JQ0KICBtdXRhdGUob3JnX2R0ID0geW1kKHBhc3RlKHlyLG1uLCcwMScsc2VwID0gJy0nKSkpDQogIA0KcHJvc19hZ2cyID0gcHJvc19kZiU+JQ0KICBtdXRhdGUoeXIgPSB5ZWFyKG9yaWdpbmF0aW9uX2RhdGUpLG1uID0gbW9udGgob3JpZ2luYXRpb25fZGF0ZSkpJT4lDQogIGdyb3VwX2J5KHlyLG1uLCBwcm9zcGVyX3JhdGluZyklPiUNCiAgI2dyb3VwX2J5KG9yaWdpbmF0aW9uX2RhdGUscHJvc3Blcl9yYXRpbmcseXIsbW4pJT4lDQogIHN1bW1hcml6ZShhdmdfYW10X2JvcnJvd2VkID0gbWVhbihhbW91bnRfYm9ycm93ZWQpLA0KICAgICAgICAgICAgd19hdmdfcmF0ZSA9IHdlaWdodGVkLm1lYW4oYm9ycm93ZXJfcmF0ZSxhbW91bnRfYm9ycm93ZWQpKSU+JQ0KICBtdXRhdGUob3JnX2R0ID0geW1kKHBhc3RlKHlyLG1uLCcwMScsc2VwID0gJy0nKSkpDQoNCg0KYGBgDQoNCg0KDQojIEEgbGl0dGxlIGFib3V0IG1lIGJlZm9yZSB3ZSBiZWdpbjoNCg0KDQpNb2RlbGVyIGF0IEJhbmsgb2YgQW1lcmljYQ0KDQpBdmlkIFN0YXIgV2FycyBGYW4NCg0KIVtdKHJldHVybl9vZl9qZWRpLmpwZykNCg0KQW1hdHVlciB1c2VyIGFuZCBhcmRlbnQgc3VwcG9ydGVyIG9mIHZlcnNpb24gY29udHJvbCBpbiBkYXRhIHNjaWVuY2UNCg0KIVtdKGdpdC5wbmcpDQoNClIgdXNlciBmb3IgdGhlIGxhc3QgOSB5ZWFycy4NCg0KVXNlZCBSIGZvciBtb2RlbGluZyBhbmQgZGV2ZWxvcG1lbnQgYXQgQW1lcmljYW4gQ3JlZGl0IEFjY2VwdGFuY2UgYmVmb3JlIGNvbWluZyB0byBCYW5rIG9mIEFtZXJpY2EuDQoNCiFbXShBQ0EucG5nKQ0KDQotIEludHJvZHVjZWQgYW5kIGNoYW1waW9uZWQgU2hpbnkgZm9yIHJhcGlkIGFwcGxpY2F0aW9uIGRldmVsb3BtZW50DQotIERldmVsb3BlZCB2YXJpb3VzIG1vZGVscyBhbmQgcHJvY2VzcyBpbXByb3ZlbWVudHMgdXNpbmcgUiB0b29sIGtpdA0KDQoqKkJhbmsgb2YgQW1lcmljYSB3b3JrOioqDQoNCiFbXShodHRwczovL3d3dy51bmRlcmNvbnNpZGVyYXRpb24uY29tL2JyYW5kbmV3L2FyY2hpdmVzL2Jhbmtfb2ZfYW1lcmljYV9sb2dvX2FuaW1hdGlvbl9uZXdfYS5naWYpDQoNCi0gTW9kZWxpbmcgTGVhZCBmb3IgQ29uc3VtZXIgQmVoYXZpb3IgTW9kZWxpbmc6DQotIFdvcmtlZCBvbiBSaXNrIE1vZGVscyBmb3IgYSB5ZWFyDQotIEN1cnJlbnRseSB3b3JraW5nIG9uIElubm92YXRpb24gdGVhbSAoQmVzdCBqb2IgSSB0aGluayBJIHdpbGwgZXZlciBoYXZlKToNCiAgLSBOZXcgbW9kZWxpbmcgaWRlYXMNCiAgLSBOZXcgYXJlYXMgb2YgdGhlIGJ1c2luZXNzIHRoYXQgd291bGQgYmVuZWZpdCBmcm9tIG1vZGVsaW5nIGtub3dsZWRnZQ0KICAtIEFwcGxpY2F0aW9uIG9mIGRlZXAgbGVhcm5pbmcNCg0KIyAqKldoYXQgaXMgdGhpcyBwcmVzZW5hdGlvbj8qKg0KDQojICoqSSBlbmpveSB0aGUgZGF0YSB2aXN1YWxpemF0aW9uIHNpZGUgb2YgZGF0YSBzY2llbmNlKioNCg0KIyMgKipnZ3Bsb3Qgd2FzIGFsd2F5cyBnbyB0b28gZm9yIG15IGRhdGEgc2NpZW5jZSBuZWVkcyoqDQoNCi0gc2ltcGxlIGludHVpdGl2ZSBzeW50YXggdG8gd3JpdGUgYW5kIHJlYWQNCi0gd2lkZSByYW5nZSBvZiB1c2VmdWwgZ2VvbXMgdG8gc29sdmUgY29tbW9uIG1vZGVsaW5nIHByb2JsZW1zDQotIGludGVncmF0ZXMgd2VsbCB3aXRoIHRoZSByZXN0IG9mIHRpZHl2ZXJzZQ0KDQotIChCaWcgc3RlcCBmb3J3YXJkIGZyb20gbGF0dGljZSBhbmQgYmFzZSBncmFwaGljcykNCg0KDQoNCiMjIyAqKmdncGxvdCBpcyBhbiBhZG9wdGlvbiBvZiB0aGUgcHJlY2VwdHMgbGFpZCBvdXQgaW4gdGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MqKiANCg0KIVtdKEdHX2NvbmNlcHRzLnBuZykNCg0KIyMgZ2dwbG90IGRvZXNuJ3QgbGVuZCBpdHNlbGYgdG8gaW50ZXJhY3Rpdml0eQ0KDQotIHNldmVyYWwgZWZmb3J0cyBoYXZlIGludHJvZHVjZWQgaW50ZXJhY3Rpdml0eSANCiAgLSBnZ3Zpcw0KICAtIHJnZ29iaQ0KICAtIGlQbG90cw0KICAtIGh0bWx3aWRnZXRzDQogIC0gcjJkMw0KDQojIGh0bWx3aWRldHMgYW5kIHIyZDMgYXJlIGFuIGFkYXB0YXRpb24gb2YgRDMvamF2YXNjcmlwdCB0ZWNobm9sb2d5DQoNCiMjIEQzIGlzIGEgamF2YXNjcmlwdCBwYWNrYWdlIHRoYXQgaXMgdmVyeSBpbXBvcnRhbnQgaW4gZGF0YSB2aXN1YWxpemF0aW9uDQoNCi0gV3JpdHRlbiBpbiBhIGxhbmd1YWdlIHRoYXQgaW50ZWdyYXRlcyB3aXRoIHdlYiBkZXZlbG9wbWVudA0KLSBVc2VzIGRhdGEgdG8gbWFuaXB1bGF0ZSB0aGUgd2ViIGRvY3VtZW50IHRocm91Z2ggdmFyaW91cyBvYmplY3RzIChTVkcpDQotIFJlbGVhc2VkIGluIDIwMTENCg0KIyMjIFVzZWZ1bCBleGFtcGxlcyBvZiBwb3dlciBhbmQgaW50ZXJhY3Rpdml0eToNCg0KIyBTdW5idXJzdCBFeGFtcGxlDQoNCiFbXShodHRwczovL2kuc3RhY2suaW1ndXIuY29tL0g2TzJLLmdpZikNCg0KIyBEZW5zaXR5IG92ZXIgdGltZQ0KDQohW10oaHR0cHM6Ly9tZWRpYS5naXBoeS5jb20vbWVkaWEvTlRqaXVza0lNRTZhd0tuMW5EL2dpcGh5LmdpZikNCg0KW0RlY2lzaW9uIFRyZWUgRXhhbXBsZV0oaHR0cDovL2JsLm9ja3Mub3JnL2ZyYWN0YWx5dGljcy9yYXcvNDk1YjYzY2Y2NzFiNGM0ODdiYzQwODAxMzY2Mzg0ZTAvKQ0KDQoNCiMgKipEMyBQcm9zIGFuZCBDb25zKioNCg0KKlByb3MqDQotIFZlcnkgZmxleGlibGUgYW5kIHBvcnRhYmxlDQotIEludGVyYWN0aXZpdHkgcGFydCBvZiB0aGUgZG5hDQotIExvb2tzIHZlcnkgcHJvZmVzc2lvbmFsIGFuZCBwb2xpc2hlZA0KDQoqQ29ucyoNCi0gVmVyeSBzdGVlcCBsZWFybmluZyBjdXJ2ZQ0KICAtIEFQSSByZXF1aXJlcyBkZWNlbnQgdW5kZXJzdGFuZGluZyBvZiBob3cgamF2YXNjcmlwdA0KLSBDZW50ZXJlZCBvbiB3ZWJkZXZlbG9wbWVudCBpbnN0ZWFkIG9mIGRhdGEgc2NpZW5jZQ0KLSA/RmFsbGluZyBvdXQgb2YgZmF2b3I/DQoNCiMjIFdoYXQgaXMgdGhlIGFuc3dlcj8NCg0KIyAqKlBsb3QubHkgaXMgYSBzb2x1dGlvbiB3aXRoIGEgc2ltcGxlciBBUEkgYW5kIG91dCBvZiB0aGUgYm94IGludGVyYWN0aXZpdHkqKg0KDQpFc3NlbnRpYWxseSBwbG90Lmx5IGlzIEFQSSB3cmFwcGVyIGZvciBzZXZlcmFsIEQzDQoNCg0KIyAqKkhvdyBkb2VzIHRoZSBwbG90bHkgcGFja2FnZSB3b3JrKioNCg0KS2V5IHRvIHVuZGVyc3RhZGluZyBwYWNrYWdlIGlzIHVuZGVyc3RhbmRpbmcgaG93IGl0IHRyYW5zZm9ybXMgdGhlIGRhdGENCg0KLSBEYXRhIGVudGVycyBpbiBSIGZvcm1hdHMNCi0gVHJhbnNmb3JtZWQgdG8gbGlzdCBmb3JtYXQgDQotIFRyYW5mb3JtZWQgdG8gSlNPTiBmb3JtYXQNCg0KQmVsb3cgaXMgYSB1c2VmdWwgZGlhZ3JhbSBzaG93aW5nIGhvdyB0aGUgZmluYWwgcHJlc2VudGF0aW9uIGlzIGRvbmUuDQoNCiFbXShwbG90bHlfZGF0YV90cmFuc2Zvcm0uc3ZnKQ0KDQoqKlBsb3RseSB1c2VzIHR3byBrZXkgY29tcG9uZW50czoqKg0KDQoxLiBEYXRhL1RyYWNlOg0KICAtIENvbm5lY3Rpb24gYmV0d2VlbiBkYXRhIGFuZCB2aXN1YWxzDQogIC0gVHJhY2VzIGhhdmUgdHlwZXMgKHNjYXR0ZXIgcGxvdCwgaGlzdG9ncmFtcywgc3VuYnVyc3QsIGV0Yy4pDQogIC0gVHJhY2UgdHlwZXMgaGF2ZSBzcGVjaWZpYyBhdHR0cmlidXRlcyB0aGF0IGNhbiBiZSBkZWZpbmVkLg0KMi4gTGF5b3V0DQoNCg0KIyAqKmdncGxvdGx5KCkgdG8gdGhlIHJlc2N1ZSoqDQoNCklmIHlvdSBoYXZlIHBsb3RzIGluIGdncGxvdCwgeW91IGNhbiBzdGFydCB1c2luZyBwbG90bHkgd2l0aCBqdXN0IGEgc2ltcGxlIGZ1bmN0aW9uIGNhbGwgb24gbW9zdCBnZ3Bsb3Qgb2JqZWN0cy4NCg0KDQpMZXQncyBtYWtlIGEgZ2dwbG90IGZyb20gUHJvcHNlciBkYXRhLiAgDQpgYGB7cn0NCnByb3Nfc2NhdCA9IHByb3NfZGZfc2FtJT4lDQogIGdncGxvdChhZXMocHJpbmNpcGFsX3BhaWQsIGludGVyZXN0X3BhaWQpKStnZW9tX3BvaW50KGFlcyhjb2xvciA9IGZhY3Rvcihwcm9zcGVyX3JhdGluZykpKQ0KDQpwcm9zX3NjYXQNCmBgYA0KDQoNCg0KYGBge3J9DQpnZ3Bsb3RseShwcm9zX3NjYXQpDQoNCmBgYA0KDQpgYGB7cn0NCg0KDQpwcm9zX2RmX3NhbSU+JQ0KICBwbG90X2x5KHggPSB+cHJpbmNpcGFsX3BhaWQsIHkgPSB+aW50ZXJlc3RfcGFpZCklPiUNCiAgYWRkX21hcmtlcnMoY29sb3IgPSB+cHJvc3Blcl9yYXRpbmcpDQpgYGANCg0KDQoNCkRvIGl0IGFnYWluLCBidXQgd2l0aCBsaW5lczoNCmBgYHtyfQ0KYW10X2Zpbl9wID0gcHJvc19hZ2clPiUNCiAgZ2dwbG90KGFlcyhvcmlnaW5hdGlvbl9kYXRlLGF2Z19hbXRfYm9ycm93ZWQpKSsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IHByb3NwZXJfcmF0aW5nKSkNCg0KYW10X2Zpbl9wDQpgYGANCg0KU2ltcGxlIGdncGxvdGx5IGNvbW1hbmQgYWRkcyB0aGUgdG9vbHRpcCwgem9vbWluZywgDQoNCmBgYHtyfQ0KZ2dwbG90bHkoYW10X2Zpbl9wKQ0KYGBgDQoNCk5vdyB0aGUgcGxvdGx5IHN5bnRheDoNCg0KYGBge3J9DQpwcm9zX2FnZyU+JQ0KICB1bmdyb3VwKCklPiUNCiAgcGxvdF9seSh4ID0gfm9yaWdpbmF0aW9uX2RhdGUsIHkgPSB+YXZnX2FtdF9ib3Jyb3dlZCwgY29sb3IgPSB+cHJvc3Blcl9yYXRpbmcsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnKQ0KIyAgYWRkX2xpbmVzKGNvbG9yID0gfnByb3NwZXJfcmF0aW5nKQ0KYGBgDQpTYW1lIEdyYXBoIGRpZmZlcmVudCBzeW50YXggKHVzaW5nIHRoZSBhZGRfKiB0cmFjZSBhZGRpdGlvbikNCmBgYHtyfQ0KcHJvc19hZ2clPiUNCiAgdW5ncm91cCgpJT4lDQogIHBsb3RfbHkoeCA9IH5vcmlnaW5hdGlvbl9kYXRlLCB5ID0gfmF2Z19hbXRfYm9ycm93ZWQpJT4lDQogIGFkZF9saW5lcyhjb2xvciA9IH5wcm9zcGVyX3JhdGluZykNCmBgYA0KDQpIaXN0b2dyYW06DQpgYGB7cn0NCnByb3NfZGZfc2FtJT4lDQogIHBsb3RfbHkoeCA9IH5ib3Jyb3dlcl9yYXRlKSU+JQ0KICBhZGRfaGlzdG9ncmFtKCkNCmBgYA0KDQpIaXN0b2dyYW0gd2l0aCBtdWlsdHBsZSBmYWN0b3JzDQpgYGB7cn0NCg0KcHJvc19kZl9zYW0lPiUNCiAgcGxvdF9seSh4ID0gfmZhY3Rvcih0ZXJtKSwgY29sb3IgPSB+cHJvc3Blcl9yYXRpbmcpJT4lDQogIGFkZF9oaXN0b2dyYW0oKQ0KDQpgYGANCg0KTm93IGxldHMgZG8gdGhpcyB3aXRoIDIgZGltZW50aW9ucw0KDQpgYGB7cn0NCg0KcHJvc19kZiU+JQ0KICBwbG90X2x5KA0KICAgIHggPSB+cHJpbmNpcGFsX3BhaWQsDQogICAgeSA9IH5pbnRlcmVzdF9wYWlkLA0KICAgIHR5cGUgPSAnaGlzdG9ncmFtMmRjb250b3VyJw0KICApDQogIA0KICANCiAgDQpgYGANCg0KYGBge3J9DQoNCnN1YnBsdCA9IHN1YnBsb3QoDQogIA0KICBwcm9zX2RmX3NhbSU+JXBsb3RfbHkoeCA9IH5wcmluY2lwYWxfcGFpZCwgY29sb3IgPSBJKCJibGFjayIpLHR5cGUgPSAnaGlzdG9ncmFtJyksDQogIHBsb3RseV9lbXB0eSgpLA0KICBwcm9zX2RmX3NhbSU+JXBsb3RfbHkoeCA9IH5wcmluY2lwYWxfcGFpZCx5ID0gfmludGVyZXN0X3BhaWQsdHlwZSA9ICdoaXN0b2dyYW0yZGNvbnRvdXInKSwNCiAgcHJvc19kZl9zYW0lPiVwbG90X2x5KHkgPSB+aW50ZXJlc3RfcGFpZCxjb2xvciA9IEkoImJsYWNrIiksdHlwZSA9ICdoaXN0b2dyYW0nKSwNCiAgbnJvd3MgPSAyLCAgDQogIGhlaWdodHMgPSBjKDAuMiwwLjgpLCANCiAgd2lkdGhzID0gYygwLjgsMC4yKSwNCiAgc2hhcmVYID0gVFJVRSwNCiAgc2hhcmVZID0gVFJVRQ0KKQ0KDQpwID0gbGF5b3V0KHN1YnBsdCwgc2hvd2xlZ2VuZCA9IEZBTFNFKQ0KICANCnANCiAgDQpgYGANCg0KDQoNCg0KYGBge3J9DQp4IDwtIHJub3JtKDEwMDApDQp5IDwtIHJub3JtKDEwMDApDQpzIDwtIHN1YnBsb3QoDQogIHBsb3RfbHkoeCA9IHgsIGNvbG9yID0gSSgiYmxhY2siKSwgdHlwZSA9ICdoaXN0b2dyYW0nKSwgDQogIHBsb3RseV9lbXB0eSgpLCANCiAgcGxvdF9seSh4ID0geCwgeSA9IHksIHR5cGUgPSAnaGlzdG9ncmFtMmRjb250b3VyJywgc2hvd3NjYWxlID0gRiksIA0KICBwbG90X2x5KHkgPSB5LCBjb2xvciA9IEkoImJsYWNrIiksIHR5cGUgPSAnaGlzdG9ncmFtJyksDQogIG5yb3dzID0gMiwgaGVpZ2h0cyA9IGMoMC4yLCAwLjgpLCB3aWR0aHMgPSBjKDAuOCwgMC4yKSwgDQogIHNoYXJlWCA9IFRSVUUsIHNoYXJlWSA9IFRSVUUsIHRpdGxlWCA9IEZBTFNFLCB0aXRsZVkgPSBGQUxTRQ0KKQ0KDQpwIDwtIGxheW91dChzLCBzaG93bGVnZW5kID0gRkFMU0UpDQoNCnANCmBgYA0KDQoNCldoYXQgZG9lcyB0aGF0IGxvb2sgbGlrZSBpbiBnZ3Bsb3Q/ICBEb2FibGUgYnV0IG5vdCBpbnR1aXRpdmUuDQpgYGB7cn0NCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KDQpoaXN0X3RvcCA8LSBwcm9zX2RmX3NhbSU+JWdncGxvdChhZXMocHJpbmNpcGFsX3BhaWQpKStnZW9tX2hpc3RvZ3JhbSgpDQplbXB0eSA8LSBnZ3Bsb3QoKStnZW9tX3BvaW50KGFlcygxLDEpLCBjb2xvdXI9IndoaXRlIikrDQogICAgICAgICB0aGVtZShheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwgICAgICAgICAgIA0KICAgICAgICAgICAgICAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSkNCg0Kc2NhdHRlciA8LSBnZ3Bsb3QoKStnZW9tX2RlbnNpdHlfMmQoYWVzKHByb3NfZGZfc2FtJHByaW5jaXBhbF9iYWxhbmNlLCBwcm9zX2RmX3NhbSRpbnRlcmVzdF9wYWlkKSkNCg0KaGlzdF9yaWdodCA8LSBwcm9zX2RmX3NhbSU+JWdncGxvdChhZXMoaW50ZXJlc3RfcGFpZCkpK2dlb21faGlzdG9ncmFtKCkrY29vcmRfZmxpcCgpDQoNCmdyaWQuYXJyYW5nZShoaXN0X3RvcCwgZW1wdHksIHNjYXR0ZXIsIGhpc3RfcmlnaHQsIG5jb2w9MiwgbnJvdz0yLCB3aWR0aHM9Yyg0LCAxKSwgaGVpZ2h0cz1jKDEsIDQpKQ0KYGBgDQoNCkFjay4gIE5vdCBhcyBnb29kLiAgDQoNCg0KTm93IHNvbWUgcGxvdHMgdGhhdCBhcmUgdmVyeSBkaWZmaWN1bHQgdG8gZG8gaW4gZ2dwbG90IGFuZCByZWx5IG9uIHRoZSBpbnRlcmFjdGl2aXR5IGhlYXZpbHkuDQoNClN1bmJ1cnN0DQoNCkZpcnN0IHN0ZXAgdG8gbWFraW5nIHRoaXMgb25lIHdvcmsgaXMgcHJvY2Vzc2luZyB0aGUgZGF0YToNCg0KTXVzdCBoYXZlIGxhYmVscywgcGFyZW50cyBhbmQgdmFsdWVzLg0KDQpXaWxsIHVzZSB0aGUgVGl0YW5pYyBkYXRhc2V0IGJlY2F1c2UgaXQgaGFzIHNvbWUgbmljZSBjYXRlZ29yaWVzIGFuZCB2YWx1ZXMgdG8gdGFrZSBvbjoNCg0KYGBge3J9DQoNCmxpYnJhcnkoZGF0YXNldHMpDQoNCnRpdGFuaWNfZGYgPSBhcy5kYXRhLmZyYW1lKFRpdGFuaWMpDQoNCnRpdGFuaWNfZGYNCmBgYA0KDQoNCmBgYHtyfQ0KYWdnMSA9IHRpdGFuaWNfZGYlPiUNCiAgZ3JvdXBfYnkoU3Vydml2ZWQpJT4lDQogIHN1bW1hcml6ZSh2YWx1ZSA9IHN1bShGcmVxKSklPiUNCiAgbXV0YXRlKGlkID0gYXMuY2hhcmFjdGVyKFN1cnZpdmVkKSwNCiAgICAgICAgIGxhYmVsID0gYXMuY2hhcmFjdGVyKFN1cnZpdmVkKSwNCiAgICAgICAgIHBhcmVudCA9ICIiKSU+JQ0KICBzZWxlY3QobGFiZWwsIGlkLCBwYXJlbnQsIHZhbHVlKQ0KDQphZ2cyID0gdGl0YW5pY19kZiU+JQ0KICBncm91cF9ieShTdXJ2aXZlZCxDbGFzcyklPiUNCiAgc3VtbWFyaXplKHZhbHVlID0gc3VtKEZyZXEpKSU+JQ0KICB1bmdyb3VwKCklPiUNCiAgbXV0YXRlKGlkID0gcGFzdGUoU3Vydml2ZWQsIENsYXNzLCBzZXAgPSAnIC0gJyksDQogICAgICAgICBwYXJlbnQgPSBhcy5jaGFyYWN0ZXIoU3Vydml2ZWQpLA0KICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoQ2xhc3MpKSU+JQ0KICBzZWxlY3QobGFiZWwsIGlkLCBwYXJlbnQsIHZhbHVlKQ0KDQphZ2czID0gdGl0YW5pY19kZiU+JQ0KICBncm91cF9ieShTdXJ2aXZlZCwgQ2xhc3MsIEFnZSklPiUNCiAgc3VtbWFyaXplKHZhbHVlID0gc3VtKEZyZXEpKSU+JQ0KICB1bmdyb3VwKCklPiUNCiAgbXV0YXRlKGlkID0gcGFzdGUoU3Vydml2ZWQsIENsYXNzLCBBZ2UsIHNlcCA9ICcgLSAnKSwNCiAgICAgICAgIHBhcmVudCA9IHBhc3RlKFN1cnZpdmVkLCBDbGFzcywgc2VwID0gJyAtICcpLA0KICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoQWdlKSklPiUNCiAgc2VsZWN0KGxhYmVsLCBpZCwgcGFyZW50LCB2YWx1ZSkNCg0KDQphZ2c0ID0gdGl0YW5pY19kZiU+JQ0KICBncm91cF9ieShTdXJ2aXZlZCxDbGFzcyxBZ2UsU2V4KSU+JQ0KICBzdW1tYXJpemUodmFsdWUgPSBzdW0oRnJlcSkpJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICBtdXRhdGUoaWQgPSBwYXN0ZShTdXJ2aXZlZCwgQ2xhc3MsIEFnZSwgU2V4LCBzZXAgPSAnIC0gJyksDQogICAgICAgICBwYXJlbnQgPSBwYXN0ZShTdXJ2aXZlZCwgQ2xhc3MsIEFnZSwgc2VwID0gJyAtICcpLA0KICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoU2V4KSklPiUNCiAgc2VsZWN0KGxhYmVsLCBpZCwgcGFyZW50LCB2YWx1ZSkNCg0KDQphZ2cgPWJpbmRfcm93cyhhZ2cxLGFnZzIpJT4lYmluZF9yb3dzKGFnZzMpJT4lYmluZF9yb3dzKGFnZzQpJT4lZmlsdGVyKHZhbHVlID4gMCkNCg0KDQoNCmFnZw0KYGBgDQoNCmBgYHtyfQ0KDQoNCnBsb3RfbHkoYWdnLA0KICAgICAgICBpZHMgPSB+aWQsDQogICAgICAgIGxhYmVscyA9IH5sYWJlbCwNCiAgICAgICAgcGFyZW50cyA9IH5wYXJlbnQsDQogICAgICAgIHZhbHVlcyA9IH52YWx1ZSwNCiAgICAgICAgdHlwZSA9ICdzdW5idXJzdCcsDQogICAgICAgIGJyYW5jaHZhbHVlcz0gJ3RvdGFsJyklPiUNCiAgbGF5b3V0KHRpdGxlID0gJ0JyZWFrZG93biBvZiBUaXRhbCBzdXJ2aXZhbCBieSBDbGFzcywgQWdlLCBhbmQgU2V4JyApDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHBsb3RseSkNCg0KZCA8LSBkYXRhLmZyYW1lKA0KICAgIGlkcyA9IGMoDQogICAgIk5vcnRoIEFtZXJpY2EiLCAiRXVyb3BlIiwgIkF1c3RyYWxpYSIsICJOb3J0aCBBbWVyaWNhIC0gRm9vdGJhbGwiLCAiU29jY2VyIiwNCiAgICAiTm9ydGggQW1lcmljYSAtIFJ1Z2J5IiwgIkV1cm9wZSAtIEZvb3RiYWxsIiwgIlJ1Z2J5IiwNCiAgICAiRXVyb3BlIC0gQW1lcmljYW4gRm9vdGJhbGwiLCJBdXN0cmFsaWEgLSBGb290YmFsbCIsICJBc3NvY2lhdGlvbiIsDQogICAgIkF1c3RyYWxpYW4gUnVsZXMiLCAiQXV0c3RyYWxpYSAtIEFtZXJpY2FuIEZvb3RiYWxsIiwgIkF1c3RyYWxpYSAtIFJ1Z2J5IiwNCiAgICAiUnVnYnkgTGVhZ3VlIiwgIlJ1Z2J5IFVuaW9uIg0KICApLA0KICBsYWJlbHMgPSBjKA0KICAgICJOb3J0aDxicj5BbWVyaWNhIiwgIkV1cm9wZSIsICJBdXN0cmFsaWEiLCAiRm9vdGJhbGwiLCAiU29jY2VyIiwgIlJ1Z2J5IiwNCiAgICAiRm9vdGJhbGwiLCAiUnVnYnkiLCAiQW1lcmljYW48YnI+Rm9vdGJhbGwiLCAiRm9vdGJhbGwiLCAiQXNzb2NpYXRpb24iLA0KICAgICJBdXN0cmFsaWFuPGJyPlJ1bGVzIiwgIkFtZXJpY2FuPGJyPkZvb3RiYWxsIiwgIlJ1Z2J5IiwgIlJ1Z2J5PGJyPkxlYWd1ZSIsDQogICAgIlJ1Z2J5PGJyPlVuaW9uIg0KICApLA0KICBwYXJlbnRzID0gYygNCiAgICAiIiwgIiIsICIiLCAiTm9ydGggQW1lcmljYSIsICJOb3J0aCBBbWVyaWNhIiwgIk5vcnRoIEFtZXJpY2EiLCAiRXVyb3BlIiwNCiAgICAiRXVyb3BlIiwgIkV1cm9wZSIsIkF1c3RyYWxpYSIsICJBdXN0cmFsaWEgLSBGb290YmFsbCIsICJBdXN0cmFsaWEgLSBGb290YmFsbCIsDQogICAgIkF1c3RyYWxpYSAtIEZvb3RiYWxsIiwgIkF1c3RyYWxpYSAtIEZvb3RiYWxsIiwgIkF1c3RyYWxpYSAtIFJ1Z2J5IiwNCiAgICAiQXVzdHJhbGlhIC0gUnVnYnkiDQogICksDQogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KKQ0KDQpwbG90X2x5KGQsIGlkcyA9IH5pZHMsIGxhYmVscyA9IH5sYWJlbHMsIHBhcmVudHMgPSB+cGFyZW50cywgdHlwZSA9ICdzdW5idXJzdCcpJT4lDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkocmpzb24pDQoNCmpzb25fZmlsZSA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Bsb3RseS9wbG90bHkuanMvbWFzdGVyL3Rlc3QvaW1hZ2UvbW9ja3Mvc2Fua2V5X2VuZXJneS5qc29uIg0KanNvbl9kYXRhIDwtIGZyb21KU09OKHBhc3RlKHJlYWRMaW5lcyhqc29uX2ZpbGUpLCBjb2xsYXBzZT0iIikpDQoNCnAgPC0gcGxvdF9seSgNCiAgICB0eXBlID0gInNhbmtleSIsDQogICAgZG9tYWluID0gbGlzdCgNCiAgICAgIHggPSAgYygwLDEpLA0KICAgICAgeSA9ICBjKDAsMSkNCiAgICApLA0KICAgIG9yaWVudGF0aW9uID0gImgiLA0KICAgIHZhbHVlZm9ybWF0ID0gIi4wZiIsDQogICAgdmFsdWVzdWZmaXggPSAiVFdoIiwNCg0KICAgIG5vZGUgPSBsaXN0KA0KICAgICAgbGFiZWwgPSBqc29uX2RhdGEkZGF0YVtbMV1dJG5vZGUkbGFiZWwsDQogICAgICBjb2xvciA9IGpzb25fZGF0YSRkYXRhW1sxXV0kbm9kZSRjb2xvciwNCiAgICAgIHBhZCA9IDE1LA0KICAgICAgdGhpY2tuZXNzID0gMTUsDQogICAgICBsaW5lID0gbGlzdCgNCiAgICAgICAgY29sb3IgPSAiYmxhY2siLA0KICAgICAgICB3aWR0aCA9IDAuNQ0KICAgICAgKQ0KICAgICksDQoNCiAgICBsaW5rID0gbGlzdCgNCiAgICAgIHNvdXJjZSA9IGpzb25fZGF0YSRkYXRhW1sxXV0kbGluayRzb3VyY2UsDQogICAgICB0YXJnZXQgPSBqc29uX2RhdGEkZGF0YVtbMV1dJGxpbmskdGFyZ2V0LA0KICAgICAgdmFsdWUgPSAganNvbl9kYXRhJGRhdGFbWzFdXSRsaW5rJHZhbHVlLA0KICAgICAgbGFiZWwgPSAganNvbl9kYXRhJGRhdGFbWzFdXSRsaW5rJGxhYmVsDQogICAgKQ0KICApICU+JSANCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gIkVuZXJneSBmb3JlY2FzdCBmb3IgMjA1MDxicj5Tb3VyY2U6IERlcGFydG1lbnQgb2YgRW5lcmd5ICYgQ2xpbWF0ZSBDaGFuZ2UsIFRvbSBDb3Vuc2VsbCB2aWEgPGEgaHJlZj0naHR0cHM6Ly9ib3N0Lm9ja3Mub3JnL21pa2Uvc2Fua2V5Lyc+TWlrZSBCb3N0b2NrPC9hPiIsDQogICAgZm9udCA9IGxpc3QoDQogICAgICBzaXplID0gMTANCiAgICApLA0KICAgIHhheGlzID0gbGlzdChzaG93Z3JpZCA9IEYsIHplcm9saW5lID0gRiksDQogICAgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRiwgemVyb2xpbmUgPSBGKQ0KKQ0KDQpwDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=